#pragma once
#include <libintl.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#ifdef TK_FRAMEWORK
#include <Tcl/tcl.h>
#include <Tk/tk.h>
#else
#include <tcl.h>
#include <tk.h>
#endif

#include "list.h"
#include "la.h"
#include "utils.h"

#define CHART_BAR 0
#define CHART_LINE 1
#define CHART_SCATTER 2

#define LA_NONE 0
#define LA_SLR 1

typedef struct SingleChart {
	int type;
	int draw_dot;
	int la_type;
	List *value;
} SingleChart;

typedef struct Chart {
	int type;
	int draw_dot;
	int la_type;
	char *label_x;
	char *label_y;
	List *value_x;
	List *value_y;
} Chart;

static int c_index = -1;
static int offset;
static Tcl_Interp *interp;

int init_tcltk() {
	interp = Tcl_CreateInterp();
	if (Tcl_Init(interp) != TCL_OK) {
		printf(gettext("Tcl_Init() raise an error: %s\n"), Tcl_GetStringResult(interp));
		return TCL_ERROR;
	}
	if (Tk_Init(interp) != TCL_OK) {
		printf(gettext("Tk_Init() raise an error: %s\n"), Tcl_GetStringResult(interp));
		return TCL_ERROR;
	}
	return TCL_OK;
}

int eval(const char *scripts) {
	return Tcl_Eval(interp, scripts);
}

int veval(const char *scripts, ...) {
	va_list args;
	va_start(args, scripts);
	char buf[128];
	vsnprintf(buf, sizeof(buf), scripts, args);
	va_end(args);
	return Tcl_Eval(interp, buf);
}

char *get_color () {
	char *table[] = {
		"blue", "red", "green",
		"cyan", "orange", NULL
	};
	c_index ++;
	if (table[c_index]) {
		return table[c_index];
	} else {
		c_index = 0;
		return table[0];
	}
}

void get_text_size(char *string, int *width, int *height) {
	Tk_Font font = Tk_GetFont(interp, Tk_MainWindow(interp), "TkDefaultFont");
	Tk_ComputeTextLayout(font, string, strlen(string), 80, TK_JUSTIFY_LEFT, TK_IGNORE_TABS, width, height);
}

void draw_axis(Chart *info) {
	eval(".canvas create line 20 20 20 481 -fill black -width 2");
	float y_max = statistic_list(info->value_y, S_MAX), y_min = statistic_list(info->value_y, S_MIN);
	if (y_min >= 0) {
		offset = 480;
		eval(".canvas create line 20 480 582 480 -fill black -width 2");
	} else {
		float y_dist = fabsf(y_min) + y_max;
		float y = fabsf(y_min) / y_dist * 460;
		offset = 480 - (int)y;
		veval(".canvas create line 20 %d 580 %d -fill black -width 2", offset, offset);
	}
}

void draw_line(int x1, int y1, int x2, int y2, char *color, int width) {
	veval(".canvas create line %d %d %d %d -fill %s -width %d", x1, y1, x2, y2, color, width);
}

void draw_rect(int x1, int y1, int x2, int y2, char *color, char *outline) {
	veval(".canvas create rectangle %d %d %d %d -fill %s -outline %s", x1, y1, x2, y2,
			color, (!outline)? color: outline);
}

void draw_dot(int x, int y, char *color) {
	draw_line(x + 5, y, x - 4, y, color, 1);
	draw_line(x, y + 5, x, y - 4, color, 1);
}

void draw_bar_chart(Chart *info) {}

void get_range(Chart *info, float *x, float *min_x, float *max_x, float *y) {
	float x_min = statistic_list(info->value_x, S_MIN);
	float x_max = statistic_list(info->value_x, S_MAX);
	float y_min = statistic_list(info->value_y, S_MIN);
	float y_max = statistic_list(info->value_y, S_MAX);
	if (x_max - x_min < 3) {
		*x = (fround(x_max, 1) + 0.1) - (fround(x_min, 1));
		*min_x = fround(x_min, 1);
		*max_x = fround(x_max, 1) + 0.1;
	} else if ((x_max - x_min >= 3) && (x_max - x_min < 100)) {
		*x = ((int)x_max + 1) - (int)x_min;
		*min_x = (int)x_min;
		*max_x = (int)x_max + 1;
	}
	if (y_max - y_min < 3) {
		*y = (fround(y_max, 1) + 0.1) - (y_min > 0)? (fround(y_min, 1)) : 0;
	} else if ((y_max - y_min >= 3) && (y_max - y_min < 100)) {
		*y = ((int)y_max + 1) - ((y_min > 0)? (int)y_min: 0);
	}
}

void draw_line_chart(Chart *info) {
	float dist_x, x_min, x_max, dist_y;
	get_range(info, &dist_x, &x_min, &x_max, &dist_y);
	char *color = get_color();
	for (int i = 0; i <= length_list(info->value_x) - 2; i++) {
		float x1 = get_list(info->value_x, i), y1 = get_list(info->value_y, i);
		int dot_x1 = (x1 / dist_x * 560) + 20;
		int dot_y1 = offset - (y1 / dist_y * (offset - 20));
		float x2 = get_list(info->value_x, i + 1), y2 = get_list(info->value_y, i + 1);
		int dot_x2 = (x2 / dist_x * 560) + 20;
		int dot_y2 = offset - (y2 / dist_y * (offset - 20));
		draw_line(dot_x1, dot_y1, dot_x2, dot_y2, color, 1);
		if (info->draw_dot) {
			draw_rect(dot_x1 + 2, dot_y1 + 2, dot_x1 - 2, dot_y1 - 2, color, NULL);
			draw_rect(dot_x2 + 2, dot_y2 + 2, dot_x2 - 2, dot_y2 - 2, color, NULL);
		}
	}
}

void draw_scatter_chart(Chart *info) {
	float dist_x, x_min, x_max, dist_y;
	get_range(info, &dist_x, &x_min, &x_max, &dist_y);
	char *color = get_color();
	for (int i = 0; i <= length_list(info->value_x) - 1; i++) {
		float x = get_list(info->value_x, i), y = get_list(info->value_y, i);
		int dot_x = ((x - x_min) / dist_x * 560) + 20;
		int dot_y = offset - (y / dist_y * (offset - 20));
		draw_dot(dot_x, dot_y, color);
	}
	if (info->la_type == LA_SLR) {
		float k, b;
		int err = simple_linear_regression(info->value_x->data, info->value_y->data,
				length_list(info->value_x), &k, &b, NULL, NULL, NULL, NULL);
		if (err < 0) {
			printf(gettext("simple_linear_regressionf() raise an error: %s\n"),
					simple_linear_regression_error_string(err));
			return;
		}
		if (b > 0) {
			printf("y = %fx + %f\n", k, b);
		} else if (b == 0) {
			printf("y = %fx", k);
		} else {
			printf("y = %fx - %f\n", k, fabsf(b));
		}
		int y1 = offset - ((b - 1) / dist_y * (offset - 20)), x2 = ((x_max - x_min) / dist_x * 560) + 20;
		float y2 = k * x_max + b;
		y2 = offset - (y2 / dist_y * (offset - 20));
		draw_line(20, y1, x2, y2, get_color(), 2);
	}
}

void draw_chart(Chart *info) {
	draw_axis(info);
	switch (info->type) {
		case CHART_BAR:
			draw_bar_chart(info);
			break;
		case CHART_LINE:
			draw_line_chart(info);
			break;
		case CHART_SCATTER:
			draw_scatter_chart(info);
			break;
	}
	c_index = -1;
}

int init_chart() {
	if (init_tcltk() != TCL_OK) {
		return -1;
	}
	int line = 0;
	const char *scripts[] = {
		"wm geometry . 600x500",
		"wm title . Counts",
		"wm resizable . 0 0",
		"canvas .canvas -width 600 -height 500 -background white",
		"grid .canvas -sticky nwes -column 0 -row 0",
		"grid columnconfigure . 0 -weight 1",
		"grid rowconfigure . 0 -weight 1",	
		NULL
	};
	Tk_MainWindow(interp);
	while (scripts[line] != NULL) {
		eval(scripts[line]);
		line ++;
	}
	return 0;
}

void mainloop() {
	Tk_MainLoop();
}

